﻿using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using IceDog.DNF.Program.BookAPI.DTOs;
using IceDog.DNF.Program.BookAPI.Models;

namespace IceDog.DNF.Program.BookAPI.Controllers
{
    //整个控制器路由前缀
    [RoutePrefix("api/books")]
    public class BooksController : ApiController
    {
        private BookAPIContext db = new BookAPIContext();

        // Typed lambda expression for Select() method. 
        private static readonly Expression<Func<Book, BookDto>> AsBookDto =
            x => new BookDto
            {
                Title = x.Title,
                Author = x.Author.Name,
                Genre = x.Genre
            };

        // Typed lambda expression for Select() method. 
        private static readonly Expression<Func<Book, BookDetailDto>> AsBookDetailDto =
            b => new BookDetailDto
            {
                Title = b.Title,
                Genre = b.Genre,
                PublishDate = b.PublishDate,
                Price = b.Price,
                Description = b.Description,
                Author = b.Author.Name
            };

        // GET: api/Books
        [Route("")]
        public IQueryable<BookDto> GetBooks()
        {
            return db.Books.Select(AsBookDto);
        }

        // GET: api/Books
        [Route("details")]
        public IQueryable<Book> GetBooksDetails()
        {
            return db.Books.Include(b=>b.Author);
        }

        // GET: api/Books/5
        [ResponseType(typeof(BookDto)), Route("{id:int}")]//参数id，解析为int类型
        public async Task<IHttpActionResult> GetBook(int id)
        {
            BookDto book = await db.Books
                .Include(b => b.Author)
                .Where(b => b.BookId == id)
                .Select(AsBookDto)
                .FirstOrDefaultAsync();
            if (book == null)
            {
                return NotFound();
            }

            return Ok(book);
        }

        [Route("{id:int}/details")]
        [ResponseType(typeof(BookDetailDto))]
        public async Task<IHttpActionResult> GetBookDetail(int id)
        {
            //var book = await (from b in db.Books.Include(b => b.Author)
            //    where b.BookId == id
            //    select new BookDetailDto
            //    {
            //        Title = b.Title,
            //        Genre = b.Genre,
            //        PublishDate = b.PublishDate,
            //        Price = b.Price,
            //        Description = b.Description,
            //        Author = b.Author.Name
            //    }).FirstOrDefaultAsync();

            var book = await db.Books.Include(b => b.Author)
                .Where(b => b.BookId == id)
                .Select(AsBookDetailDto)
                .FirstOrDefaultAsync();
            if (book == null)
            {
                return NotFound();
            }
            return Ok(book);
        }

        [Route("{genre}")]
        public IQueryable<BookDto> GetBooksByGenre(string genre)
        {
            return db.Books.Include(b => b.Author)
                .Where(b => b.Genre.Equals(genre, StringComparison.OrdinalIgnoreCase))
                .Select(AsBookDto);
        }

        [Route("~/api/authors/{authorId:int}/books")]//重新定义路由为此路由
        public IQueryable<BookDto> GetBooksByAuthor(int authorId)
        {
            return db.Books.Include(b => b.Author)
                .Where(b => b.AuthorId == authorId)
                .Select(AsBookDto);
        }

        //[Route("date/{pubdate:datetime}")]
        //严格匹配日期格式yyyy-mm-dd
        [Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
        // yyyy/mm/dd,因为路由和日期都是/分隔，所以需要在占位符前加上*，强制跨越多个片段进行解析
        [Route("date/{*pubdate:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")] 
        public IQueryable<BookDto> GetBooks(DateTime pubdate)
        {
            return db.Books.Include(b => b.Author)
                .Where(b => DbFunctions.TruncateTime(b.PublishDate)
                            == DbFunctions.TruncateTime(pubdate))
                .Select(AsBookDto);
        }


        // PUT: api/Books/5
        [ResponseType(typeof(void))]
        public async Task<IHttpActionResult> PutBook(int id, Book book)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != book.BookId)
            {
                return BadRequest();
            }

            db.Entry(book).State = EntityState.Modified;

            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!BookExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return StatusCode(HttpStatusCode.NoContent);
        }

        // POST: api/Books
        [ResponseType(typeof(Book))]
        public async Task<IHttpActionResult> PostBook(Book book)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Books.Add(book);
            await db.SaveChangesAsync();

            return CreatedAtRoute("DefaultApi", new { id = book.BookId }, book);
        }

        // DELETE: api/Books/5
        [ResponseType(typeof(Book))]
        public async Task<IHttpActionResult> DeleteBook(int id)
        {
            Book book = await db.Books.FindAsync(id);
            if (book == null)
            {
                return NotFound();
            }

            db.Books.Remove(book);
            await db.SaveChangesAsync();

            return Ok(book);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }

        private bool BookExists(int id)
        {
            return db.Books.Count(e => e.BookId == id) > 0;
        }
    }
}